/*
 * 
 * 
 * 
 */
package com.hboxs.asl.plugin;

import com.hboxs.asl.Setting;
import com.hboxs.asl.entity.Payment;
import com.hboxs.asl.entity.PluginConfig;
import com.hboxs.asl.service.PaymentService;
import com.hboxs.asl.service.PluginConfigService;
import com.hboxs.asl.util.SettingUtils;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.CompareToBuilder;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * Plugin - 支付
 */
public abstract class PaymentPlugin implements Comparable<PaymentPlugin> {

    /**
     * 支付方式名称属性名称
     */
    public static final String PAYMENT_NAME_ATTRIBUTE_NAME = "paymentName";

    /**
     * 手续费类型属性名称
     */
    public static final String FEE_TYPE_ATTRIBUTE_NAME = "feeType";

    /**
     * 手续费属性名称
     */
    public static final String FEE_ATTRIBUTE_NAME = "fee";

    /**
     * LOGO属性名称
     */
    public static final String LOGO_ATTRIBUTE_NAME = "logo";

    /**
     * 描述属性名称
     */
    public static final String DESCRIPTION_ATTRIBUTE_NAME = "description";

    /**
     * 手续费类型
     */
    public enum FeeType {

        /**
         * 按比例收费
         */
        scale,

        /**
         * 固定收费
         */
        fixed
    }

    /**
     * 请求方法
     */
    public enum RequestMethod {

        /**
         * POST
         */
        post,

        /**
         * GET
         */
        get
    }

    /**
     * 通知方法
     */
    public enum NotifyMethod {

        /**
         * 通用
         */
        general,

        /**
         * 同步
         */
        sync,

        /**
         * 异步
         */
        async
    }

    @Resource(name = "pluginConfigServiceImpl")
    private PluginConfigService pluginConfigService;
    @Resource(name = "paymentServiceImpl")
    private PaymentService paymentService;

    /**
     * 获取ID
     *
     * @return ID
     */
    public final String getId() {
        return getClass().getAnnotation(Component.class).value();
    }

    /**
     * 获取名称
     *
     * @return 名称
     */
    public abstract String getName();

    /**
     * 获取版本
     *
     * @return 版本
     */
    public abstract String getVersion();

    /**
     * 获取作者
     *
     * @return 作者
     */
    public abstract String getAuthor();

    /**
     * 获取网址
     *
     * @return 网址
     */
    public abstract String getSiteUrl();

    /**
     * 获取安装URL
     *
     * @return 安装URL
     */
    public abstract String getInstallUrl();

    /**
     * 获取un安装URL
     *
     * @return un安装URL
     */
    public abstract String getUninstallUrl();

    /**
     * 获取设置URL
     *
     * @return 设置URL
     */
    public abstract String getSettingUrl();

    /**
     * 获取是否已安装
     *
     * @return 是否已安装
     */
    public boolean getIsInstalled() {
        return pluginConfigService.pluginIdExists(getId());
    }

    /**
     * 获取插件配置
     *
     * @return 插件配置
     */
    public PluginConfig getPluginConfig() {
        return pluginConfigService.findByPluginId(getId());
    }

    /**
     * 获取是否已启用
     *
     * @return 是否已启用
     */
    public boolean getIsEnabled() {
        PluginConfig pluginConfig = getPluginConfig();
        return pluginConfig != null ? pluginConfig.getIsEnabled() : false;
    }

    /**
     * 获取属性值
     *
     * @param name 属性名称
     * @return 属性值
     */
    public String getAttribute(String name) {
        PluginConfig pluginConfig = getPluginConfig();
        return pluginConfig != null ? pluginConfig.getAttribute(name) : null;
    }

    /**
     * 获取排序
     *
     * @return 排序
     */
    public Integer getOrder() {
        PluginConfig pluginConfig = getPluginConfig();
        return pluginConfig != null ? pluginConfig.getOrder() : null;
    }

    /**
     * 获取支付方式名称
     *
     * @return 支付方式名称
     */
    public String getPaymentName() {
        PluginConfig pluginConfig = getPluginConfig();
        return pluginConfig != null ? pluginConfig.getAttribute(PAYMENT_NAME_ATTRIBUTE_NAME) : null;
    }

    /**
     * 获取手续费类型
     *
     * @return 手续费类型
     */
    public FeeType getFeeType() {
        PluginConfig pluginConfig = getPluginConfig();
        return pluginConfig != null ? FeeType.valueOf(pluginConfig.getAttribute(FEE_TYPE_ATTRIBUTE_NAME)) : null;
    }

    /**
     * 获取手续费
     *
     * @return 手续费
     */
    public BigDecimal getFee() {
        PluginConfig pluginConfig = getPluginConfig();
        return pluginConfig != null ? new BigDecimal(pluginConfig.getAttribute(FEE_ATTRIBUTE_NAME)) : null;
    }

    /**
     * 获取LOGO
     *
     * @return LOGO
     */
    public String getLogo() {
        PluginConfig pluginConfig = getPluginConfig();
        return pluginConfig != null ? pluginConfig.getAttribute(LOGO_ATTRIBUTE_NAME) : null;
    }

    /**
     * 获取描述
     *
     * @return 描述
     */
    public String getDescription() {
        PluginConfig pluginConfig = getPluginConfig();
        return pluginConfig != null ? pluginConfig.getAttribute(DESCRIPTION_ATTRIBUTE_NAME) : null;
    }

    /**
     * 获取请求URL
     *
     * @return 请求URL
     */
    public abstract String getRequestUrl();

    /**
     * 获取请求方法
     *
     * @return 请求方法
     */
    public abstract RequestMethod getRequestMethod();

    /**
     * 获取请求字符编码
     *
     * @return 请求字符编码
     */
    public abstract String getRequestCharset();

    /**
     * 获取请求参数
     *
     * @param sn          编号
     * @param description 描述
     * @param request     httpServletRequest
     * @return 请求参数
     */
    public abstract Map<String, Object> getParameterMap(String sn, String description, HttpServletRequest request);

    /**
     * 验证通知是否合法
     *
     * @param sn           编号
     * @param notifyMethod 通知方法
     * @param request      httpServletRequest
     * @return 通知是否合法
     */
    public abstract boolean verifyNotify(String sn, NotifyMethod notifyMethod, HttpServletRequest request);

    /**
     * 获取通知返回消息
     *
     * @param sn           编号
     * @param notifyMethod 通知方法
     * @param request      httpServletRequest
     * @return 通知返回消息
     */
    public abstract String getNotifyMessage(String sn, NotifyMethod notifyMethod, HttpServletRequest request);

    /**
     * 获取超时时间
     *
     * @return 超时时间
     */
    public abstract Integer getTimeout();

    /**
     * 计算支付手续费
     *
     * @param amount 金额
     * @return 支付手续费
     */
    public BigDecimal calculateFee(BigDecimal amount) {
        Setting setting = SettingUtils.get();
        BigDecimal fee;
        if (getFeeType() == FeeType.scale) {
            fee = amount.multiply(getFee());
        } else {
            fee = getFee();
        }
        return setting.setScale(fee);
    }

    /**
     * 计算支付金额
     *
     * @param amount 金额
     * @return 支付金额
     */
    public BigDecimal calculateAmount(BigDecimal amount) {
        return amount.add(calculateFee(amount)).setScale(2, RoundingMode.UP);
    }

    /**
     * 根据编号查找收款单
     *
     * @param sn 编号(忽略大小写)
     * @return 收款单，若不存在则返回null
     */
    protected Payment getPayment(String sn) {
        return paymentService.findBySn(sn);
    }

    /**
     * 获取通知URL
     *
     * @param sn           编号
     * @param notifyMethod 通知方法
     * @return 通知URL
     */
    protected String getNotifyUrl(String sn, NotifyMethod notifyMethod) {
        Setting setting = SettingUtils.get();
        if (notifyMethod == null) {
            return setting.getSiteUrl() + "/payment/notify/" + NotifyMethod.general + "/" + sn + ".htm";
        }
        return setting.getSiteUrl() + "/payment/notify/" + notifyMethod + "/" + sn + ".htm";
    }

    /**
     * 连接Map键值对
     *
     * @param map              Map
     * @param prefix           前缀
     * @param suffix           后缀
     * @param separator        连接符
     * @param ignoreEmptyValue 忽略空值
     * @param ignoreKeys       忽略Key
     * @return 字符串
     */
    protected String joinKeyValue(Map<String, Object> map, String prefix, String suffix, String separator, boolean ignoreEmptyValue, String... ignoreKeys) {
        List<String> list = new ArrayList<String>();
        if (map != null) {
            for (Entry<String, Object> entry : map.entrySet()) {
                String key = entry.getKey();
                String value = ConvertUtils.convert(entry.getValue());
                if (StringUtils.isNotEmpty(key) && !ArrayUtils.contains(ignoreKeys, key) && (!ignoreEmptyValue || StringUtils.isNotEmpty(value))) {
                    list.add(key + "=" + (value != null ? value : ""));
                }
            }
        }
        return (prefix != null ? prefix : "") + StringUtils.join(list, separator) + (suffix != null ? suffix : "");
    }

    /**
     * 连接Map值
     *
     * @param map              Map
     * @param prefix           前缀
     * @param suffix           后缀
     * @param separator        连接符
     * @param ignoreEmptyValue 忽略空值
     * @param ignoreKeys       忽略Key
     * @return 字符串
     */
    protected String joinValue(Map<String, Object> map, String prefix, String suffix, String separator, boolean ignoreEmptyValue, String... ignoreKeys) {
        List<String> list = new ArrayList<String>();
        if (map != null) {
            for (Entry<String, Object> entry : map.entrySet()) {
                String key = entry.getKey();
                String value = ConvertUtils.convert(entry.getValue());
                if (StringUtils.isNotEmpty(key) && !ArrayUtils.contains(ignoreKeys, key) && (!ignoreEmptyValue || StringUtils.isNotEmpty(value))) {
                    list.add(value != null ? value : "");
                }
            }
        }
        return (prefix != null ? prefix : "") + StringUtils.join(list, separator) + (suffix != null ? suffix : "");
    }

    /**
     * POST请求
     *
     * @param url          URL
     * @param parameterMap 请求参数
     * @return 返回结果
     */
    protected String post(String url, Map<String, Object> parameterMap) {
        Assert.hasText(url);
        String result = null;
        HttpClient httpClient = new DefaultHttpClient();
        try {
            HttpPost httpPost = new HttpPost(url);
            List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
            if (parameterMap != null) {
                for (Entry<String, Object> entry : parameterMap.entrySet()) {
                    String name = entry.getKey();
                    String value = ConvertUtils.convert(entry.getValue());
                    if (StringUtils.isNotEmpty(name)) {
                        nameValuePairs.add(new BasicNameValuePair(name, value));
                    }
                }
            }
            httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, "UTF-8"));
            HttpResponse httpResponse = httpClient.execute(httpPost);
            HttpEntity httpEntity = httpResponse.getEntity();
            result = EntityUtils.toString(httpEntity);
            EntityUtils.consume(httpEntity);
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            httpClient.getConnectionManager().shutdown();
        }
        return result;
    }

    /**
     * GET请求
     *
     * @param url          URL
     * @param parameterMap 请求参数
     * @return 返回结果
     */
    protected String get(String url, Map<String, Object> parameterMap) {
        Assert.hasText(url);
        String result = null;
        HttpClient httpClient = new DefaultHttpClient();
        try {
            List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
            if (parameterMap != null) {
                for (Entry<String, Object> entry : parameterMap.entrySet()) {
                    String name = entry.getKey();
                    String value = ConvertUtils.convert(entry.getValue());
                    if (StringUtils.isNotEmpty(name)) {
                        nameValuePairs.add(new BasicNameValuePair(name, value));
                    }
                }
            }
            HttpGet httpGet = new HttpGet(url + (StringUtils.contains(url, "?") ? "&" : "?") + EntityUtils.toString(new UrlEncodedFormEntity(nameValuePairs, "UTF-8")));
            HttpResponse httpResponse = httpClient.execute(httpGet);
            HttpEntity httpEntity = httpResponse.getEntity();
            result = EntityUtils.toString(httpEntity);
            EntityUtils.consume(httpEntity);
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            httpClient.getConnectionManager().shutdown();
        }
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        if (this == obj) {
            return true;
        }
        PaymentPlugin other = (PaymentPlugin) obj;
        return new EqualsBuilder().append(getId(), other.getId()).isEquals();
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder(17, 37).append(getId()).toHashCode();
    }

    public int compareTo(PaymentPlugin paymentPlugin) {
        return new CompareToBuilder().append(getOrder(), paymentPlugin.getOrder()).append(getId(), paymentPlugin.getId()).toComparison();
    }

}